﻿/*************************************************************************
 * Created by OhZee Creative, LLC
 * 2013
 * This code is licensed under Microsoft Public License (Ms-PL)
 * http://opensource.org/licenses/ms-pl
 * http://ozframework.codeplex.com/
 *************************************************************************/

namespace OhZeeCreative.Windows8
{
    using OhZeeCreative.Helpers;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Windows.Input;

    public abstract class MonitoringDelegateCommandBase : ICommand, INotifyPropertyChanged, IDisposable
    {
        protected bool isStarted;
        protected readonly INotifyPropertyChanged viewModel;
        protected readonly string[] properties;
        protected readonly IDictionary<string, object> monitor;

        private bool isEnabled;
        public bool IsEnabled
        {
            get { return this.isEnabled; }
            set
            {
                if (this.isEnabled != value)
                {
                    this.isEnabled = value;
                    if (this.CanExecuteChanged != null)
                    {
                        this.CanExecuteChanged(this, EventArgs.Empty);
                    }
                    if (this.PropertyChanged != null)
                    {
                        this.PropertyChanged(this, new PropertyChangedEventArgs("IsEnabled"));
                    }
                }
            }
        }

        private bool isVisible;
        public bool IsVisible
        {
            get { return this.isVisible; }
            set
            {
                if (this.isVisible != value)
                {
                    this.isVisible = value;
                    if (this.PropertyChanged != null)
                    {
                        this.PropertyChanged(this, new PropertyChangedEventArgs("IsVisible"));
                    }
                }
            }
        }

        public virtual bool CanExecute(object parameter)
        {
            return this.IsEnabled;
        }

        public event EventHandler CanExecuteChanged;

        public abstract void Execute(object parameter);

        public event PropertyChangedEventHandler PropertyChanged;

        public MonitoringDelegateCommandBase(INotifyPropertyChanged viewModel, params string[] properties)
        {
            this.viewModel = viewModel;
            this.properties = properties;

            if (this.viewModel == null || properties.Length == 0)
            {
                throw new ArgumentException("StartMonitor is called with incorrect arguments");
            }

            this.monitor = new Dictionary<string, object>(properties.Length);

            foreach (string property in properties)
            {
                this.monitor.Add(property, null);
            }

            this.StartMonitor();
        }

        private void RefreshPropertyValues()
        {
            foreach (string property in this.properties)
            {
                this.monitor[property] = ReflectionHelper.GetPropertyValue(this.viewModel, property);
            }
        }

        private void NullifyPropertyValues()
        {
            foreach (string property in this.properties)
            {
                this.monitor[property] = null;
            }
        }

        private void ViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            string propertyName = e.PropertyName;
            object oldValue;

            if (this.monitor.TryGetValue(propertyName, out oldValue))
            {
                object newValue = ReflectionHelper.GetPropertyValue(this.viewModel, propertyName);
                this.monitor[propertyName] = newValue;
                this.OnMonitorUpdate(e.PropertyName, oldValue, newValue);
            }
        }

        protected virtual void OnMonitorUpdate(string propertyChanged, object oldValue, object newValue)
        {
            // Implement in the derived class
        }

        public void StartMonitor()
        {
            if (this.isStarted)
            {
                throw new InvalidOperationException("Monitor is already started");
            }

            this.isStarted = true;
            this.viewModel.PropertyChanged += ViewModelPropertyChanged;
            this.RefreshPropertyValues();
        }

        public void StopMonitor()
        {
            if (this.isStarted)
            {
                this.viewModel.PropertyChanged -= ViewModelPropertyChanged;
                this.NullifyPropertyValues();
                this.isStarted = false;
            }
        }

        public void Dispose()
        {
            this.StopMonitor();
        }
    }

    public class MonitoringDelegateCommand : MonitoringDelegateCommandBase
    {
        private Action action;

        public MonitoringDelegateCommand(Action action, INotifyPropertyChanged viewModel, params string[] properties)
            : base(viewModel, properties)
        {
            this.action = action;
        }

        public override void Execute(object parameter)
        {
            if (this.CanExecute(parameter))
            {
                this.action();
            }
        }
    }

    public class MonitoringDelegateCommand<T> : MonitoringDelegateCommandBase
    {
        private Action<T> action;

        public MonitoringDelegateCommand(Action<T> action, INotifyPropertyChanged viewModel, params string[] properties)
            : base(viewModel, properties)
        {
            this.action = action;
        }

        public override void Execute(object parameter)
        {
            if (this.CanExecute(parameter))
            {
                this.action((T)parameter);
            }
        }
    }
}
